fi.internetix.validation.ValidationDelegator = Class.create(
  /** @lends fi.internetix.validation.ValidationDelegator# */ 
  {
  /**
  * Class constructor
  * @class Validation delegator for single field
  * @constructs
  */
  initialize : function(field) {
    this._field = field;
    this._fieldChangeListener = this._onFieldChange.bindAsEventListener(this);
  
    if (field.type == 'checkbox') {
      Event.observe(field, 'click', this._fieldChangeListener);
    } else {
      Event.observe(field, 'keyup', this._fieldChangeListener);
      Event.observe(field, 'change', this._fieldChangeListener);
    }
    this._validators = new Array();
  },
  /**
   * Class deinitialization
   */
  deinitialize: function () {
    this._validators.clear();
    
    if (this._field.type == 'checkbox') {
      Event.stopObserving(this._field, 'click', this._fieldChangeListener);
    } else {
      Event.stopObserving(this._field, 'keyup', this._fieldChangeListener);
      Event.stopObserving(this._field, 'change', this._fieldChangeListener);
    }
    
    delete this._validators;
    this._validators = undefined;
    this._field = undefined;
  },
  /**
   * Adds new validator for field
   * 
   * @param validator validator
   */
  addValidator: function (validator) {
    this._validators.push(validator);
  },
  /**
   * Validates field
   * 
   * @param requiredCheckAsUnknown whether required should be interpret as unknown
   */
  validate: function (requiredCheckAsUnknown) {
    this._validate(requiredCheckAsUnknown, this._validators, fi.internetix.validation.FieldValidator.STATUS_UNKNOWN);
  },
  /**
   * Resets field validity before validation
   * 
   * @param requiredCheckAsUnknown whether required should be interpret as unknown
   */
  forceValidate: function (requiredCheckAsUnknown) {
    this._field._validity = undefined;
    this.validate(requiredCheckAsUnknown);
  },
  /**
   * Returns field validity status
   * @returns field validity status
   */
  getStatus: function () {
    return this._field._validity == undefined ? fi.internetix.validation.FieldValidator.STATUS_UNDEFINED : this._field._validity;
  },
  /**
   * Retruns whether field is mandatory or not
   * 
   * @returns {Boolean} true if field is mandatory
   */
  isMandatory: function () {
    for (var i = this._validators.length - 1; i >= 0; i--) {
      if (this._validators[i].getType() == fi.internetix.validation.FieldValidator.TYPE_MANDATORY)
        return true;
    }
    
    return false;
  },
  /**
   * Returns whether field is inside form  
   * 
   * @param formElement form
   * @returns {Boolean} whether field is inside form  
   */
  insideForm: function (formElement) {
    return this._field.form == formElement;
  },
  _isVisible: function () {
    var e = $(this._field);
    
    if (e.getAttribute('type') == 'hidden')
      return false;
    
    while (e.parentNode) {
      if (Object.isFunction(e.visible) && !e.visible())
        return false;
      
      e = $(e.parentNode);
    }
    
    return true;
  },
  _validate: function (requiredCheckAsUnknown, validators, initialStatus) {
    var oldStatus = this.getStatus();
    var status = initialStatus;
    var visible = this._isVisible();

    if (visible) {
      for (var i = 0, l = validators.length; i < l; i++) {
        if (status != fi.internetix.validation.FieldValidator.STATUS_INVALID) {
          switch (validators[i].validate(this._field)) {
            case fi.internetix.validation.FieldValidator.STATUS_INVALID:
              if (requiredCheckAsUnknown && (validators[i].getType() == fi.internetix.validation.FieldValidator.TYPE_MANDATORY))
                status = fi.internetix.validation.FieldValidator.STATUS_UNKNOWN;
              else
                status = fi.internetix.validation.FieldValidator.STATUS_INVALID;
            break;
            case fi.internetix.validation.FieldValidator.STATUS_VALID:
              status = fi.internetix.validation.FieldValidator.STATUS_VALID;
            break;
          }
        } else {
          break;
        }
      }
      
      if (status != fi.internetix.validation.FieldValidator.STATUS_UNKNOWN) {
        var linkedValidators = this._getLinkedValidators(validators);
        for (var i = 0, l = linkedValidators.length; i < l; i++) {
          var linkedField = linkedValidators[i].getLinkedField(this._field);
          var fieldDelegator = fi.internetix.validation.ValidationDelegatorVault.getDelegator(linkedField);
          fieldDelegator._linkedValidate(requiredCheckAsUnknown, status, linkedValidators[i]);
        }
      }
    } else {
      status = fi.internetix.validation.FieldValidator.STATUS_HIDDEN;
    }
    
    if (oldStatus != status) {
      this._field._validity = status;
      
      switch (status) {
        case fi.internetix.validation.FieldValidator.STATUS_UNKNOWN:
          if (oldStatus == fi.internetix.validation.FieldValidator.STATUS_VALID)
            this._field.removeClassName('valid');
          else 
            this._field.removeClassName('invalid');
          __formValidationHook(this._field.form, this.isMandatory());          
        break;
        case fi.internetix.validation.FieldValidator.STATUS_INVALID:
          if (oldStatus == fi.internetix.validation.FieldValidator.STATUS_VALID)
            this._field.removeClassName('valid');
          this._field.addClassName('invalid');
          __formValidationHook(this._field.form, true);
        break;
        case fi.internetix.validation.FieldValidator.STATUS_VALID:
          if (oldStatus != fi.internetix.validation.FieldValidator.STATUS_VALID)
            this._field.removeClassName('invalid');
          this._field.addClassName('valid');
          __formValidationHook(this._field.form, false);
        break;
        case fi.internetix.validation.FieldValidator.STATUS_HIDDEN:
          this._field.removeClassName('invalid');
          this._field.removeClassName('valid');
          __formValidationHook(this._field.form, false);
        break;
      }
    } 
  },
  _getLinkedValidators: function (validators) {
    var result = new Array();
    for (var i = 0, l = validators.length; i < l; i++) {
      var validator = validators[i];
      if (validator.getType() == fi.internetix.validation.FieldValidator.TYPE_LINKED) 
        result.push(validator);
    }
    
    return result;
  },
  _linkedValidate: function (requiredCheckAsUnknown, status, linkedValidator) {
    this._validate(requiredCheckAsUnknown, this._validators.without(linkedValidator), status);
  },
  _onFieldChange: function (event) {
    this.validate(false);
  }
});

function __formValidationHook(formElement, isInvalid) {
  if (formElement) {
    formElement = $(formElement);
    var formValidButton = formElement.down(".formvalid");
    if (formValidButton) {
      var valid = !isInvalid;

      if (valid) {
        var delegators = fi.internetix.validation.ValidationDelegatorVault.getFormDelegators(formElement);
        for ( var i = 0, l = delegators.length; i < l; i++) {
          switch (delegators[i].getStatus()) {
            case fi.internetix.validation.FieldValidator.STATUS_INVALID:
              valid = false;
            break;
            case fi.internetix.validation.FieldValidator.STATUS_UNKNOWN:
              if (delegators[i].isMandatory()) {
                valid = false;
              }
            break;
          }

          if (valid == false) {
            break;
          }
        }
      }

      if (valid)
        formValidButton.removeAttribute("disabled");
      else
        formValidButton.setAttribute("disabled", "disabled");
    }
  }
};